// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Plossum.CommandLine;
using CIP4.Common;
using CIP4.Serializer;

namespace CIP4.CMD
{
    class CMDProgram
    {
        public const string APPNAME = "CIP4.CMD";

        static OptionsType _options;

        static StreamWriter _logFile;
        static StreamWriter LogFile
        {
            get
            {
                if (_logFile == null)
                {
                    _logFile = string.IsNullOrEmpty(_options.LogFileNameQualifier) ?
                        new StreamWriter(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + string.Format("\\{0}.log", APPNAME), true) :
                        new StreamWriter(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + string.Format("\\{0}.{1}.log", APPNAME, _options.LogFileNameQualifier), true);

                    AlwaysWriteLog(MajorEnum.Information, ">>>>>> Starting " + APPNAME);
                }
                return _logFile;
            }
        }

        static DateTime _startTime;

        static void Main()
        {
            _startTime = DateTime.Now;

            if (!CheckArgs())
            {
                Environment.ExitCode = 8000;
                return;
            }
            AlwaysWriteLog(MajorEnum.Information, _options.ToString());

            if (_options.Confirm)
            {
                ShowInformational(_options.ToString());
                ShowInformational("Enter Yes to run with these options, No to exit");
                string response = Console.ReadLine();
                if (response != null)
                    if (!(response.ToLower() == "y" || response.ToLower() == "yes"))
                    {
                        Environment.ExitCode = 1000;
                        return;
                    }
            }

            if (_options.Action == OptionsType.ActionEnum.None)
            {
                ShowWarning("Nothing to do");

            }

            if (_options.Action == OptionsType.ActionEnum.Generate)
            {
                GeneratorBuilderType.Generate(SerializerKindEnum.CIP4Json, _options.AssemblyFile, Path.GetDirectoryName(_options.AssemblyFile), _options.TypeNameValues, Logger);
                GeneratorBuilderType.Generate(SerializerKindEnum.CIP4Xml, _options.AssemblyFile, Path.GetDirectoryName(_options.AssemblyFile), _options.TypeNameValues, Logger);
            }

            if (_options.Action == OptionsType.ActionEnum.Build)
            {
                GeneratorBuilderType.GenerateAndBuild(SerializerKindEnum.CIP4Json, _options.AssemblyFile, Path.GetDirectoryName(_options.AssemblyFile), _options.TypeNameValues, Logger);
                GeneratorBuilderType.GenerateAndBuild(SerializerKindEnum.CIP4Xml, _options.AssemblyFile, Path.GetDirectoryName(_options.AssemblyFile), _options.TypeNameValues, Logger);
            }
            else if (_options.Action == OptionsType.ActionEnum.Monitor)
            {
                WrapperMonitor();
                Console.WriteLine(
                    "Enter \n" + 
                    "'status [optional collection name]' for current status\n" +
                    "'cycle [optional collection name]' to force the wrapper to close and re-open its DataProvider provider\n" +
                    "'pause [optional collection name]' to stop the wrapper accepting requests from the client\n" +
                    "'resume [optional collection name]' to cancel a pause\n" +
                    "'clear [optional collection name]' to clear current wrapper control files\n" +
                    "'cls' to clear the console display\n" +
                    "'exit' or Control^C to stop");
                WrapperCommand("status", string.Empty);
                string response;
                string[] parts = { string.Empty };
                do
                {
                    Console.WriteLine("Monitoring...");
                    response = Console.ReadLine();
                    if (response != null) parts = response.Split(new[] { ' ' });
                    if (string.Compare(parts[0], "status", true) == 0)
                        WrapperCommand("status", parts.Length > 1? parts[1] : string.Empty);
                    else if (string.Compare(parts[0], "cycle", true) == 0)
                        WrapperCommand("cycle", parts.Length > 1 ? parts[1] : string.Empty);
                    else if (string.Compare(parts[0], "pause", true) == 0)
                        WrapperCommand("pause", parts.Length > 1 ? parts[1] : string.Empty);
                    else if (string.Compare(parts[0], "resume", true) == 0)
                        WrapperCommand("resume", parts.Length > 1 ? parts[1] : string.Empty);
                    else if (string.Compare(parts[0], "clear", true) == 0)
                        WrapperClear();
                    else if (string.Compare(parts[0], "cls", true) == 0)
                        Console.Clear();
                } while (string.Compare(parts[0], "exit", true) != 0);
            }
            else if (_options.Action == OptionsType.ActionEnum.Status || 
                _options.Action == OptionsType.ActionEnum.Cycle || 
                _options.Action == OptionsType.ActionEnum.Pause || 
                _options.Action == OptionsType.ActionEnum.Resume)
            {
                if (_options.CollectionNameValues.Count == 0)
                    WrapperCommand(_options.Action.ToString(), string.Empty);
                else
                    foreach (string collectionName in _options.CollectionNameValues)
                        WrapperCommand(_options.Action.ToString(), collectionName);
            }
            else if (_options.Action == OptionsType.ActionEnum.Clear)
            {
                WrapperClear();
            }

        }

        private static readonly Dictionary<string, CommandFileType> WatchingThese = new Dictionary<string, CommandFileType>();

        private static bool _clearing;

        private static string GetCollectionFromFileName(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
                return string.Empty;
            string fileName = Path.GetFileName(filePath);
            if (fileName != null)
                return fileName.Split(new[] { '.' })[0];
            return string.Empty;
        }

        private static void WrapperCommand(string command, string forCollection)
        {
            foreach (FileInfo fi in new DirectoryInfo(CommandFileType.GetCommandFilesLocation()).GetFiles("*.command.txt"))
            {
                if (!string.IsNullOrEmpty(forCollection) && forCollection != GetCollectionFromFileName(fi.FullName))
                    continue;
                if (!WatchingThese.ContainsKey(fi.FullName))
                {
                    CommandFileType cft = new CommandFileType(
                        "Commander", fi.FullName, GetCollectionFromFileName(fi.FullName), WatchCommandChanged);
                    WatchingThese.Add(fi.FullName, cft);
                    cft.Start();
                }
                CommandFileType cf = WatchingThese[fi.FullName];
                cf.WriteCommand(command);
            }
        }

        private static void WatchCommandChanged(object sender, FileSystemEventArgs e)
        {
            if (_clearing)
                return;
            string collectionName = GetCollectionFromFileName(e.FullPath);
            if (_options.CollectionNameValues.Count > 0 && !_options.CollectionNameValues.Any(cn => cn == collectionName))
                return;
            if (!WatchingThese.ContainsKey(e.FullPath))
            {
                CommandFileType cft = new CommandFileType(
                    "Commander", e.FullPath, GetCollectionFromFileName(e.FullPath), WatchCommandChanged);
                WatchingThese.Add(e.FullPath, cft);
                cft.Start();
            }
            CommandFileType cf = WatchingThese[e.FullPath];
            string text = cf.ReadLast();
            if (!string.IsNullOrEmpty(text))
                Console.WriteLine(text);
        }

        private static void WrapperClear()
        {
            try
            {
                _clearing = true;
                foreach (FileInfo fi in new DirectoryInfo(CommandFileType.GetCommandFilesLocation()).GetFiles("*.command.txt"))
                {
                    if (!WatchingThese.ContainsKey(fi.FullName))
                    {
                        CommandFileType cft = new CommandFileType(
                            "Commander", fi.FullName, GetCollectionFromFileName(fi.FullName), WatchCommandChanged);
                        WatchingThese.Add(fi.FullName, cft);
                        cft.Start();
                    }
                    CommandFileType cf = WatchingThese[fi.FullName];
                    cf.Clear();
                }
            }
            finally
            {
                _clearing = false;
            }
        }

        private static FileSystemWatcher _commandDirectoryWatcher;

        private static void WrapperMonitor()
        {
            foreach (FileInfo fi in new DirectoryInfo(CommandFileType.GetCommandFilesLocation()).GetFiles("*.command.txt"))
            {
                if (WatchingThese.ContainsKey(fi.FullName))
                    continue;
                string collectionName = GetCollectionFromFileName(fi.FullName);
                if (_options.CollectionNameValues.Count > 0 && !_options.CollectionNameValues.Any(cn => cn == collectionName))
                    return;
                CommandFileType cft = new CommandFileType(
                    "Commander", fi.FullName, GetCollectionFromFileName(fi.FullName), WatchCommandChanged);
                WatchingThese.Add(fi.FullName, cft);
                cft.Start();
            }

            _commandDirectoryWatcher = new FileSystemWatcher();

            _commandDirectoryWatcher.Path = CommandFileType.GetCommandFilesLocation();

            _commandDirectoryWatcher.Created += CommandDirectoryWatcherCreated;

            _commandDirectoryWatcher.EnableRaisingEvents = true;
        }

        static void CommandDirectoryWatcherCreated(object sender, FileSystemEventArgs e)
        {
            if (!WatchingThese.ContainsKey(e.FullPath))
            {
                CommandFileType cft = new CommandFileType(
                    "Commander", e.FullPath, GetCollectionFromFileName(e.FullPath), WatchCommandChanged);
                WatchingThese.Add(e.FullPath, cft);
                cft.Start();
                Console.WriteLine("Now watching " + e.FullPath);
            }
        }

        #region message handlers

        private static LoggerResponseEnum Logger(MajorEnum entryType, MinorEnum minorEntryType, string message, object[] paras)
        {
            if (_options.LoggingLevel >= entryType)
            {
                lock (LogFile)
                {
                    string fullMessage = string.Format(message, paras);
                    if (entryType == MajorEnum.Information)
                        ShowInformational(fullMessage);
                    else if (entryType == MajorEnum.Warning)
                        ShowWarning(fullMessage);
                    else if (entryType == MajorEnum.Error)
                        ShowError(fullMessage);
                    else if (entryType == MajorEnum.Noise)
                        Console.Write(string.Format("\r{0} - {1}",
                            TimeSpanString(DateTime.Now - _startTime),
                            fullMessage.Substring(0, Math.Min(Console.WindowWidth - 12, fullMessage.Length))));
                    else 
                        Console.WriteLine(fullMessage);
                    WriteLog(MajorEnum.Information, fullMessage);
                }
            }
            if (entryType != MajorEnum.Error)
                return LoggerResponseEnum.keepGoing;
            return LoggerResponseEnum.giveUp;
        }

        private static string TimeSpanString(TimeSpan value)
        {
            return string.Format("{0,2:00}:{1,2:00}:{2,2:00}", value.Hours, value.Minutes, value.Seconds);
        }

        private static void ShowError(string message)
        {
            lock (LogFile)
            {
                WriteLog(MajorEnum.Error, message);
                Console.BackgroundColor = ConsoleColor.DarkRed;
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine(message);
                Console.ResetColor();
            }
        }

        private static void ShowWarning(string message)
        {
            lock (LogFile)
            {
                if (_options.LoggingLevel >= MajorEnum.Warning)
                    WriteLog(MajorEnum.Warning, message);
                Console.BackgroundColor = ConsoleColor.DarkYellow;
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine(message);
                Console.ResetColor();
            }
        }

        private static void ShowInformational(string message)
        {
            Console.WriteLine(message);
        }

        private static string _lastMessage;

        private static void WriteLog(MajorEnum level, string message)
        {
            if (string.IsNullOrEmpty(message) || message.Trim().Length == 0 || message.Trim() == _lastMessage)
                return;
            _lastMessage = message.Trim();
            AlwaysWriteLog(level, message);
        }

        private static void AlwaysWriteLog(MajorEnum level, string message)
        {
            _lastMessage = message.Trim();
            LogFile.WriteLine(string.Format("{0} - {1}: {2}", DateTime.Now.ToString("MM:dd hh:mm:ss.fff"), level, message.Trim()));
            LogFile.Flush();
        }

        private static bool CheckArgs()
        {
            bool result = true;
            _options = new OptionsType();

            CommandLineParser parser = new CommandLineParser(_options);
            parser.Parse();

            if (parser.HasErrors)
            {
                Console.BackgroundColor = ConsoleColor.DarkRed;
                Console.ForegroundColor = ConsoleColor.White;
                foreach (object err in parser.Errors)
                    Console.WriteLine(err.ToString());
                Console.ResetColor();
                result = false; 
            }
            if (parser.RemainingArguments.Count > 0)
            {
                Console.BackgroundColor = ConsoleColor.DarkRed;
                Console.ForegroundColor = ConsoleColor.White;
                Console.Write("Unrecognized input: ");
                foreach (string s in parser.RemainingArguments)
                    Console.Write(s + " ");
                Console.WriteLine();
                Console.ResetColor();
                result = false; 
            }

            if (!string.IsNullOrEmpty(_options.Host))
            {
                if (_options.Action == OptionsType.ActionEnum.Build || _options.Action == OptionsType.ActionEnum.Generate)
                {
                    result = false;
                    ShowError("Option host cannot be used with /action build or /action generate");
                }
            }
            else
            {
                _options.Host = Environment.MachineName;
            }

            Console.WriteLine(parser.UsageInfo.GetHeaderAsString(GetWidth() - 2));

            if (_options.Help)
            {
                Console.WriteLine(parser.UsageInfo.GetOptionsAsString(GetWidth() - 2));
                Console.WriteLine("Defaults:");
                Console.WriteLine(_options.ToString());
                Console.WriteLine(string.Format("Logging to {0}", Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + string.Format("\\{0}.log", APPNAME)));
                result = false; 
            }

            return result;
        }

        private static int GetWidth()
        {
            try
            {
                return Console.WindowWidth;
            }
            catch
            {
                return 72;
            }
        }

        #endregion
    }
}
